package com.info33.platform.common.util;

import java.util.Date;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;

import org.apache.commons.lang3.StringUtils;

/**
 * 发送邮件工具类
 *
 * @author Administrator
 */
public class SendMail {
    // 发件人的 邮箱 和 密码（替换为自己的邮箱和密码）
    // PS: 某些邮箱服务器为了增加邮箱本身密码的安全性，给 SMTP 客户端设置了独立密码（有的邮箱称为“授权码”）,
    //     对于开启了独立密码的邮箱, 这里的邮箱密码必需使用这个独立密码（授权码）。
    public static String myEmailAccount = "1373813128@qq.com";
    public static String myEmailPassword = "mmlbjvkdvdpeieig";
 
    // 发件人邮箱的 SMTP 服务器地址, 必须准确, 不同邮件服务器地址不同, 一般(只是一般, 绝非绝对)格式为: smtp.xxx.com
    // 网易126邮箱的 SMTP 服务器地址为: smtp.126.com
    public static String myEmailSMTPHost = "smtp.qq.com";
 
    /**
     * 发送邮件
     * @param theme 主题（必填）
     * @param content 内容（必填）
     * @param receiveMailAccount 收件箱（必填），多个收件箱中间用逗号隔开
     * @param attachmentUrl 附件地址（选填），多个附件中间用逗号隔开
     * @throws Exception
     */
    public static void sendMail(String theme, String content, String receiveMailAccount, String attachmentUrl) throws Exception {
    	 // 1. 创建参数配置, 用于连接邮件服务器的参数配置
        Properties props = new Properties();                    // 参数配置
        props.setProperty("mail.transport.protocol", "smtp");   // 使用的协议（JavaMail规范要求）
        props.setProperty("mail.smtp.host", myEmailSMTPHost);   // 发件人的邮箱的 SMTP 服务器地址
        props.setProperty("mail.smtp.auth", "true");            // 需要请求认证
 
        // PS: 某些邮箱服务器要求 SMTP 连接需要使用 SSL 安全认证 (为了提高安全性, 邮箱支持SSL连接, 也可以自己开启),
        //     如果无法连接邮件服务器, 仔细查看控制台打印的 log, 如果有有类似 “连接失败, 要求 SSL 安全连接” 等错误,
        //     取消下面 /* ... */ 之间的注释代码, 开启 SSL 安全连接。
        /*
        // SMTP 服务器的端口 (非 SSL 连接的端口一般默认为 25, 可以不添加, 如果开启了 SSL 连接,
        //                  需要改为对应邮箱的 SMTP 服务器的端口, 具体可查看对应邮箱服务的帮助,
        //                  QQ邮箱的SMTP(SLL)端口为465或587, 其他邮箱自行去查看)
        final String smtpPort = "465";
        props.setProperty("mail.smtp.port", smtpPort);
        props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.setProperty("mail.smtp.socketFactory.fallback", "false");
        props.setProperty("mail.smtp.socketFactory.port", smtpPort);
        */
 
        // 2. 根据配置创建会话对象, 用于和邮件服务器交互
        Session session = Session.getInstance(props);
        // 设置为debug模式, 可以查看详细的发送 log
        session.setDebug(true);
 
        // 3. 创建一封邮件
        MimeMessage message = createMimeMessage(session, myEmailAccount, theme, receiveMailAccount, content, attachmentUrl);
 
        // 4. 根据 Session 获取邮件传输对象
        Transport transport = session.getTransport();
 
        // 5. 使用 邮箱账号 和 密码 连接邮件服务器, 这里认证的邮箱必须与 message 中的发件人邮箱一致, 否则报错
        //
        //    PS_01: 如果连接服务器失败, 都会在控制台输出相应失败原因的log。
        //    仔细查看失败原因, 有些邮箱服务器会返回错误码或查看错误类型的链接,
        //    根据给出的错误类型到对应邮件服务器的帮助网站上查看具体失败原因。
        //
        //    PS_02: 连接失败的原因通常为以下几点, 仔细检查代码:
        //           (1) 邮箱没有开启 SMTP 服务;
        //           (2) 邮箱密码错误, 例如某些邮箱开启了独立密码;
        //           (3) 邮箱服务器要求必须要使用 SSL 安全连接;
        //           (4) 请求过于频繁或其他原因, 被邮件服务器拒绝服务;
        //           (5) 如果以上几点都确定无误, 到邮件服务器网站查找帮助。
        //
        transport.connect(myEmailAccount, myEmailPassword);
 
        // 6. 发送邮件, 发到所有的收件地址, message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
        transport.sendMessage(message, message.getAllRecipients());
 
        // 7. 关闭连接
        transport.close();
    }
 
    /**
     * 创建一封只包含文本的简单邮件
     *
     * @param session     和服务器交互的会话
     * @param sendMail    发件人邮箱
     * @param receiveMail 收件人邮箱
     * @return
     * @throws Exception
     */
    public static MimeMessage createMimeMessage(Session session, String sendMail, String theme, String receiveMail, String content, String attachmentUrl) throws Exception {
    	MimeMultipart mm = new MimeMultipart();
        // 1. 创建一封邮件
        MimeMessage message = new MimeMessage(session);
 
        // 2. From: 发件人
        message.setFrom(new InternetAddress(sendMail));
 
        String[] arr = receiveMail.split(",");
        // 3. To: 收件人（可以增加多个收件人、抄送、密送）
        message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(arr[0], "UTF-8"));
        
        for(int i = 1; i < arr.length; i++) {
            //To: 增加收件人（可选）
            message.addRecipient(MimeMessage.RecipientType.TO, new InternetAddress(arr[i], "UTF-8"));
        }
        
        // 4. Subject: 邮件主题
        message.setSubject(theme, "UTF-8");
        
        // 5. 创建文本"节点"
        MimeBodyPart text = new MimeBodyPart();
        // 这里添加图片的方式是将整个图片包含到邮件内容中, 实际上也可以以 http 链接的形式添加网络图片
        text.setContent(content, "text/html;charset=UTF-8");
        mm.addBodyPart(text);
        
        if(!StringUtils.isEmpty(attachmentUrl)) {
        	 String[] attUrl = attachmentUrl.split(",");
        	 for(int i = 0; i < attUrl.length; i++) {
        		 // 6. 创建附件"节点"
                 MimeBodyPart attachment = new MimeBodyPart();
                 // 读取本地文件
                 DataHandler dh = new DataHandler(new FileDataSource(attUrl[i]));
                 // 将附件数据添加到"节点"
                 attachment.setDataHandler(dh);
                 // 设置附件的文件名（需要编码）
                 attachment.setFileName(MimeUtility.encodeText(dh.getName()));       
                 // 7. 设置（文本+图片）和 附件 的关系（合成一个大的混合"节点" / Multipart ）
                 mm.addBodyPart(attachment);  
        	 }
        }
       
        // 混合关系
        mm.setSubType("mixed");         
        
        // 8. Content: 邮件正文（可以使用html标签）
        message.setContent(mm);
        
        // 9. 设置发件时间
        message.setSentDate(new Date());
 
        // 10. 保存设置
        message.saveChanges();
 
        return message;
    }
    
    public static void main(String[] args) throws Exception {
    	String content = "<p>\r\n"
		+ "	<img src=\"http://119.3.169.92:9488/pc/202012022131580377.JPG\" /> \r\n"
		+ "</p>\r\n"
		+ "<p>\r\n"
		+ "	<br />\r\n"
		+ "</p>\r\n"
		+ "<p style=\"box-sizing:border-box;margin-top:0px;margin-bottom:24px;max-width:85ch;color:#1B1B1B;font-family:arial, x-locale-body, sans-serif;font-size:16px;letter-spacing:-0.0444444px;white-space:normal;background-color:#FFFFFF;\">\r\n"
		+ "	Array.from()&nbsp;可以通过以下方式来创建数组对象：\r\n"
		+ "</p>\r\n"
		+ "<ul style=\"box-sizing:border-box;max-width:85ch;color:#1B1B1B;font-family:arial, x-locale-body, sans-serif;font-size:16px;letter-spacing:-0.0444444px;white-space:normal;background-color:#FFFFFF;\">\r\n"
		+ "	<li style=\"box-sizing:border-box;margin:0px 0px 8px;\">\r\n"
		+ "		伪数组对象（拥有一个&nbsp;length&nbsp;属性和若干索引属性的任意对象）\r\n"
		+ "	</li>\r\n"
		+ "	<li style=\"box-sizing:border-box;margin:0px 0px 8px;\">\r\n"
		+ "		<a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols\" style=\"box-sizing:border-box;color:#005282;text-decoration-skip-ink:auto;\">可迭代对象</a>（可以获取对象中的元素,如 Map和 Set 等）\r\n"
		+ "	</li>\r\n"
		+ "</ul>\r\n"
		+ "<p style=\"box-sizing:border-box;margin-top:0px;margin-bottom:24px;max-width:85ch;color:#1B1B1B;font-family:arial, x-locale-body, sans-serif;font-size:16px;letter-spacing:-0.0444444px;white-space:normal;background-color:#FFFFFF;\">\r\n"
		+ "	Array.from()&nbsp;方法有一个可选参数&nbsp;mapFn，让你可以在最后生成的数组上再执行一次&nbsp;<a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map\" style=\"box-sizing:border-box;color:#005282;text-decoration-skip-ink:auto;\">map</a>&nbsp;方法后再返回。也就是说&nbsp;Array.from(obj, mapFn, thisArg)&nbsp;就相当于&nbsp;Array.from(obj).map(mapFn, thisArg),&nbsp;除非创建的不是可用的中间数组。 这对一些数组的子类,如&nbsp;<a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays\" style=\"box-sizing:border-box;color:#005282;text-decoration-skip-ink:auto;\">typed arrays</a><span>&nbsp;来说很重要,</span>&nbsp;因为中间数组的值在调用 map() 时需要是适当的类型。\r\n"
		+ "</p>\r\n"
		+ "<p style=\"box-sizing:border-box;margin-top:0px;margin-bottom:24px;max-width:85ch;color:#1B1B1B;font-family:arial, x-locale-body, sans-serif;font-size:16px;letter-spacing:-0.0444444px;white-space:normal;background-color:#FFFFFF;\">\r\n"
		+ "	from()&nbsp;的&nbsp;length&nbsp;属性为 1 ，即&nbsp;Array.from.length === 1。\r\n"
		+ "</p>\r\n"
		+ "<p style=\"box-sizing:border-box;margin-top:0px;margin-bottom:24px;max-width:85ch;color:#1B1B1B;font-family:arial, x-locale-body, sans-serif;font-size:16px;letter-spacing:-0.0444444px;white-space:normal;background-color:#FFFFFF;\">\r\n"
		+ "	在 ES2015 中，&nbsp;Class&nbsp;语法允许我们为内置类型（比如&nbsp;Array）和自定义类新建子类（比如叫&nbsp;SubArray）。这些子类也会继承父类的静态方法，比如&nbsp;SubArray.from()，调用该方法后会返回子类&nbsp;SubArray&nbsp;的一个实例，而不是&nbsp;Array&nbsp;的实例。\r\n"
		+ "</p>\r\n"
		+ "<p>\r\n"
		+ "	<br />\r\n"
		+ "</p>";
        SendMail.sendMail("测试邮件", content, "1373813128@qq.com","D:\\upload_files\\smart_platform\\BUSINESS\\messageNotification\\2021\\1632290857691cb8440fc971984022a3bfdec.html");
    }
}
